[Python] 最小限setup.pyでのビルドを通じてsetuptoolsの気持ちを聞いてみた
Glueを使っていて、Pythonの自作スクリプトを使用するためにwheelファイルを作成しています。
これをきっかけに、setup.py
ファイルの記述内容について理解しておこうと思いました。
こういった時は私はいつも最小限のコードを作ることから始めています。 最小限のコードで動かすと、 そのライブラリや仕組みから「こういう使い方がデフォルトなんだよ!」という声を聞くことができます。 やたらと省略可能引数をたくさんつけて実行するということは、 それだけ元から想定されていたデフォルトから外れたことをしているんだと気づくための指標になります。 (必ずしもそうではない場合もあることは承知していますが)
ということで今回はsetup.py
の記述を最小限にした時の挙動確認をやってみました。
検証環境
- Python 3.11.2
最小限の構成
hoge
モジュールをもつapp_common
パッケージを作ろうと思います。
ディレクトリ構成はこんな感じです。
$ tree . . ├── app_common │ ├── __init__.py │ └── hoge.py └── setup.py
__init__.py
はパッケージには必ず必要なものになりますので、
中身は空で良いので配置する必要があります。
最小限のsetup.py
はこんな感じです。
from setuptools import setup setup()
setup()
を呼んでいるだけですね、はい。
これの実行方法は
$ python setup.py bdist_wheel
です。
bdist_wheel
は、出力としてビルドされたwheel形式のものを出すという意味です。
ちなみにsetup()
に明示的に渡されていないからちょっと気持ち悪いですが、
setup()
の中でsys.argv
が使われています。
ビルドした後のカレントディレクトリの様子はこんな感じです。
$ tree . ├── app_common │ ├── __init__.py │ └── hoge.py ├── app_common.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ └── top_level.txt ├── build │ ├── bdist.macosx-12-arm64 │ └── lib │ └── app_common │ ├── __init__.py │ └── hoge.py ├── dist │ └── app_common-0.0.0-py3-none-any.whl └── setup.py
whlファイルの正体はzipファイルなので、unzipして中身を見るとこうなっています。
├── app_common │ ├── __init__.py │ └── hoge.py └── app_common-0.0.0.dist-info ├── METADATA ├── RECORD ├── WHEEL └── top_level.txt
app_common
ディレクトリの中身がそのまま入っていることがわかります。
whlファイルがsys.path
に配置されている場合、
このディレクトリがimport対象として記述できることになります。
最小限の時の挙動
カレントディレクトリにあるパッケージをビルドします。
具体的には、カレントディレクトリ直下の__init__.py
を含むディレクトリを探し、
それをPythonパッケージと見なしてパッケージングします。
app_common
というディレクトリをパッケージングした時の
出来上がるwheelファイルはこうなっています。
app_common-0.0.0-py3-none-any.whl
setup
に特に何も指定していないので、
- パッケージ名はディレクトリと同じ
- バージョンは
0.0.0
で作られることがわかります。
今回はピュアPythonを想定していますので、ABIタグ(none
になっている部分)やアーキテクチャ(any
になっている部分)については特に触れません
(嘘です、私がそれほど詳しく知りません)が、広くほとんどの環境で動くものとなります。
パッケージングの際の備考
__init__.py
パッケージングされるディレクトリは __init__.py
を含む必要があります。
正確には、含まなくてもwheelファイルの生成自体は成功しますが、
中にあるhoge.py
はwheelには含まれませんので、やりたいことは全く達成できていません。
ディレクトリ名
ディレクトリ名がそのままパッケージ名になりますので、
パッケージ名として使えない文字が入っているディレクトリは無視されるようです。
例えばapp_common.tmp
は対象となりません。
ディレクトリの数
この最小限指定では、パッケージングできるディレクトリは一つだけです。 もし複数のディレクトリがパッケージングできる条件を満たしていた場合、 エラーが出てビルドが失敗します。
setupの引数
setupの動きを知る上での最低限の引数だけ少し確認してみます。
name
パッケージの名前を指定します。 ディレクトリと違う名前をつけたい時は指定します。 というか、常に指定した方がいいでしょう。
あくまでもパッケージ名なので、
例えばpackage_name
なんて名前をつけたとしても、
このパッケージをimportする場合は
# import package_name ではなく import app_common
となります。
packages
ライブラリに含めるパッケージを指定します。 デフォルトでは1つのパッケージしか含めることができませんでしたが、 ここで引数を指定することで複数パッケージを含めることができます。
これを指定すれば複数パッケージを含められるのであれば、 「指定しなくても自動的に見つけたディレクトリを全部含めてくれればいいのに」と思わなくもないですね。 一つのwheelファイルに複数のパッケージを入れることは可能という事実と、 どのパッケージを入れるのかを明示的に書くことが自然であることから、 これも必須で指定した方が良さそうです。
なお、name
を指定しないでpackages
を指定した場合は、
パッケージ名はUNKNOWN
となるようです。
include_package_data
通常、ビルドした成果物には.py
などの一部のファイルしか格納されません。
(含まれる物としては MANIFEST.in を使ってソースコード配布物にファイルを含める を参照してください。)
MANIFEST.in
ファイルに、含めるファイルを定義し、
include_package_data=True
を指定することで自由にファイルを含めることができます。
MANIFESTファイルの書き方についてはやはり上記公式ドキュメントを参照して欲しいのですが、
ポイントとしては、MANIFESTファイルの記述に加えてinclude_package_data=True
の指定も必要ということです。
想定されたファイル形式以外はライブラリに含めたくないという意思を感じますね。
まとめ
setup.py
の記述を最小限にした際の挙動についてまとめてみました。
無理に最小限にする必要はない(特にname
とか)ですが、
デフォルトの動きがわかると、そのメソッドなどがどういう思想で作られているのかがわかるので、
それを知った上で利用すると理解が深まりますし、納得感があって良いですね!
誰かのお役に立てば幸いです!